package cn.conac.rc.gateway.security;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.mobile.device.Device;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import cn.conac.rc.framework.utils.BeanMapper;
import cn.conac.rc.framework.utils.StringUtils;
import cn.conac.rc.gateway.security.vo.JwtUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

@Component
public class JwtTokenUtil implements Serializable {

    private static final long serialVersionUID = -3301605591108950415L;

    private static final String CLAIM_KEY_USERNAME = "sub";
    private static final String CLAIM_KEY_AUDIENCE = "audience";
    private static final String CLAIM_KEY_CREATED = "created";
    public static final String CLAIM_KEY_USER = "user";

    private static final String AUDIENCE_UNKNOWN = "unknown";
    private static final String AUDIENCE_WEB = "web";
    private static final String AUDIENCE_MOBILE = "mobile";
    private static final String AUDIENCE_TABLET = "tablet";

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private Long expiration;

    /**
     * 根据用户信息生成JWT Token
     * @param userDetails
     * @param device
     * @return
     */
    public String generateToken(JwtUser userDetails, Device device) {
        Map<String, Object> claims = new HashMap<>();
        String sub = userDetails.getId();
        String org = userDetails.getOrgName();
        if (userDetails.getUserType() == 3) {
            // 委办局取用户名
            sub = userDetails.getUsername();
        } else {
            if ("1".equals(userDetails.getIsAdmin())) {
                // 机构帐号只显示部门名称，或中文域名？
                sub = org;
                if(sub == null){
                    //TODO 未绑定部门，需要批量处理一下 
                    sub = userDetails.getUsername();
                }
            } else {
                // 编制帐号
                if (StringUtils.isNotBlank(userDetails.getUsername()))
                    sub = userDetails.getUsername();
                if (StringUtils.isNotBlank(userDetails.getFullName()))
                    sub = userDetails.getFullName();
                /* if(org!=null)
                    sub = sub + "(" + org + ")";*/
            }
        }
        

        claims.put(CLAIM_KEY_USERNAME, sub);
        claims.put(CLAIM_KEY_AUDIENCE, generateAudience(device));
        claims.put(CLAIM_KEY_CREATED, new Date());
        claims.put(CLAIM_KEY_USER, userDetails);
        return generateToken(claims);
    }

    /**
     * 重新生成token，主要用于延长失效时间
     * @param token
     * @return 新的token
     */
    public String refreshToken(String token) {
        String refreshedToken;
        try {
            final Claims claims = getClaimsFromToken(token);
            claims.put(CLAIM_KEY_CREATED, new Date());
            refreshedToken = generateToken(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    /**
     * 校验token的有效性
     * @param token
     * @param userDetails
     * @return 是否有效
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        JwtUser user = (JwtUser) userDetails;
        final String username = getUsernameFromToken(token);
        final Date created = getCreatedDateFromToken(token);
        // final Date expiration = getExpirationDateFromToken(token);
        return (username.equals(user.getUsername()) && !isTokenExpired(token)
                && !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate()));
    }

    /**
     * @param token
     * @param lastPasswordReset
     * @return token是否可以更新，移动端不受失效时间限制
     */
    public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
        final Date created = getCreatedDateFromToken(token);
        return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
                && (!isTokenExpired(token) || ignoreTokenExpiration(token));
    }

    /**
     * @param token
     * @return 从token中获得用户名，对应sub
     */
    public String getUsernameFromToken(String token) {
        String username;
        try {
            final Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * @param token
     * @return token的创建时间
     */
    public Date getCreatedDateFromToken(String token) {
        Date created;
        try {
            final Claims claims = getClaimsFromToken(token);
            created = new Date((Long) claims.get(CLAIM_KEY_CREATED));
        } catch (Exception e) {
            created = null;
        }
        return created;
    }

    /**
     * @param token
     * @return token的失效时间
     */
    public Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }

    /**
     * @param token
     * @return token的用户信息，对应jwtuser
     */
    public JwtUser getUserFromToken(String token) {
        JwtUser user;
        try {
            final Claims claims = getClaimsFromToken(token);
            HashMap map = (HashMap) claims.get(JwtTokenUtil.CLAIM_KEY_USER);
            List<HashMap> tmp = (List<HashMap>) map.remove("authorities");
            user = BeanMapper.map(map, JwtUser.class);
            List<SimpleGrantedAuthority> list = new ArrayList<>(tmp.size());
            for (HashMap obj : tmp) {
                list.add(new SimpleGrantedAuthority(obj.get("authority").toString()));
            }
            user.setAuthorities(list);
        } catch (Exception e) {
            user = null;
        }
        return user;
    }

    /**
     * @param token
     * @return token中的终端类型，对应audience
     */
    public String getAudienceFromToken(String token) {
        String audience;
        try {
            final Claims claims = getClaimsFromToken(token);
            audience = (String) claims.get(CLAIM_KEY_AUDIENCE);
        } catch (Exception e) {
            audience = null;
        }
        return audience;
    }

    public Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    /**
     * 根据不同的终端 设置不同的超时时间<br>
     * web为1小时, 移动端为10天
     * @param device
     * @return
     */
    private Date generateExpirationDate(String device) {
        // if (device == null || AUDIENCE_WEB.equals(device))
        // return new Date(System.currentTimeMillis() + 12 * 3600 * 1000l);
        // else
        return new Date(System.currentTimeMillis() + 10 * 86400 * 1000l);
    }

    @SuppressWarnings("unused")
    private Date generateExpirationDate() {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

    public Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
        return (lastPasswordReset != null && created.before(lastPasswordReset));
    }

    private String generateAudience(Device device) {
        String audience = AUDIENCE_UNKNOWN;
        if (device.isNormal()) {
            audience = AUDIENCE_WEB;
        } else if (device.isTablet()) {
            audience = AUDIENCE_TABLET;
        } else if (device.isMobile()) {
            audience = AUDIENCE_MOBILE;
        }
        return audience;
    }

    private Boolean ignoreTokenExpiration(String token) {
        String audience = getAudienceFromToken(token);
        return (AUDIENCE_TABLET.equals(audience) || AUDIENCE_MOBILE.equals(audience));
    }

    private String generateToken(Map<String, Object> claims) {
        return Jwts.builder().setClaims(claims)
                .setExpiration(generateExpirationDate((String) claims.get(CLAIM_KEY_AUDIENCE)))
                .signWith(SignatureAlgorithm.HS512, secret).compact();
    }
}